﻿using SharpDepend.Classes;
using SharpDepend.Datatypes;
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;

namespace SharpDepend.WinRT.Renderer
{
    /// <summary>
    /// Interaction logic for SoftwareRenderer.
    /// </summary>
    public class SoftwareRenderer : UserControl, IGraphicsDevice
    {
        private WriteableBitmap wBitmap;
        private BuiltInGraphicsHost mBuiltInGraphicsHost;
        private Image mImage;
        private Stream stream;
        private Windows.UI.Xaml.DispatcherTimer timer;
        private System.Diagnostics.Stopwatch mStopwatch = new System.Diagnostics.Stopwatch();

        private byte[] result = new byte[0];
        private int resultLength = 0;

        public event Action ResolutionChanged;
        public event Action OnManualRender;

        /// <summary>
        /// Create instance of software renderer.
        /// </summary>
        public SoftwareRenderer()
        {
            mBuiltInGraphicsHost = new BuiltInGraphicsHost(4, 4);
            mImage = new Image();
            Content = mImage;

            VerticalAlignment = Windows.UI.Xaml.VerticalAlignment.Stretch;
            VerticalContentAlignment = Windows.UI.Xaml.VerticalAlignment.Stretch;
            HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Stretch;
            HorizontalContentAlignment = Windows.UI.Xaml.HorizontalAlignment.Stretch;

            mImage.SizeChanged += SoftwareRenderer_SizeChanged;
            mImage.Loaded += SoftwareRenderer_Loaded;
            mImage.Stretch = Windows.UI.Xaml.Media.Stretch.Fill;

            //timer = SharpDepend.Manager.GUITimerManager.CreateTimer();
            //timer.Interval = new TimeSpan(0, 0, 0, 33);
            //timer.Tick += Timer_Tick;
            //timer.Start();

            // TODO: SharpDepend.Manager.GUITimer was bad. Seems that Tick has really really
            //       low priory so it calls one per minute.
            //       Create a new GuiTImer for WinRT instead of XamarinForms.WinRT timer.
            timer = new DispatcherTimer();
            timer.Interval = new TimeSpan(0, 0, 0, 0, 33);
            timer.Tick += Click_Tick;
            timer.Start();
        }

        private void Click_Tick(object sender, object e)
        {
            Timer_Tick();
        }

        private void Timer_Tick()
        {
            // Write backbuffer.
            var lStream = stream;
            if (lStream != null)
            {
                lStream.Position = 0;
                lStream.Write(result, 0, resultLength);

                wBitmap.Invalidate();
            }
        }

        void SoftwareRenderer_Loaded(object sender, RoutedEventArgs e)
        {
            if (wBitmap == null)
            {
                // Only set first time. It will update the sizeChanged event and update with real size.
                // This code is very bad so it need to be more clearly later.
                SetViewportSize(4, 4);
            }
        }

        private bool IsAnotherSize(double width, double height)
        {
            return (int)width != mBuiltInGraphicsHost.RenderWidth || (int)height != mBuiltInGraphicsHost.RenderHeight;
        }

        public void SetViewportSize(double width, double height)
        {
            if (IsAnotherSize(width, height))
            {
                width = Math.Max(4, (int)width);
                height = Math.Max(4, (int)height);

                wBitmap = new WriteableBitmap((int)width, (int)height);
                mBuiltInGraphicsHost.SetViewportSize((int)width, (int)height);
                mImage.Source = wBitmap;
                stream = wBitmap.PixelBuffer.AsStream();

                var call = ResolutionChanged;
                if (call != null)
                {
                    call();
                }
            }
        }

        void SoftwareRenderer_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            SetViewportSize(e.NewSize.Width, e.NewSize.Height);
        }

        public object Locker
        {
            get { return mBuiltInGraphicsHost.Locker; }
        }

        public bool ManualRender
        {
            get { return true; }
        }

        public int RenderWidth
        {
            get { return mBuiltInGraphicsHost.RenderWidth; }
        }

        public int RenderHeight
        {
            get { return mBuiltInGraphicsHost.RenderHeight; }
        }

        public bool Enable
        {
            get
            {
                return mBuiltInGraphicsHost.Enable;
            }
            set
            {
                mBuiltInGraphicsHost.Enable = value;
            }
        }

        public bool RenderTextures
        {
            get
            {
                return mBuiltInGraphicsHost.RenderTextures;
            }
            set
            {
                mBuiltInGraphicsHost.RenderTextures = value;
            }
        }

        public void RegisterTexture(Texture texture)
        {
            mBuiltInGraphicsHost.RegisterTexture(texture);
        }

        public void UnregisterTexture(Texture texture)
        {
            mBuiltInGraphicsHost.UnregisterTexture(texture);
        }

        public void UpdateTexture(Texture texture)
        {
            mBuiltInGraphicsHost.UpdateTexture(texture);
        }

        public void BeginRender()
        {
            mBuiltInGraphicsHost.BeginRender();
        }

        public void SetClip(Vector4 plane)
        {
            mBuiltInGraphicsHost.SetClip(plane);
        }

        public void ResetClip()
        {
            mBuiltInGraphicsHost.ResetClip();
        }

        public void Draw(ref Vector4 rectangle, SharpDepend.Datatypes.Color color)
        {
            mBuiltInGraphicsHost.Draw(ref rectangle, color);
        }

        public void Draw(ref Vector4 rectangle, SharpDepend.Datatypes.Color color, Texture texture, ref Vector4 destinationTextureUV)
        {
            mBuiltInGraphicsHost.Draw(ref rectangle, color, texture, ref destinationTextureUV);
        }

        public void DrawLine(Vector2 start, Vector2 end, SharpDepend.Datatypes.Color color)
        {
            mBuiltInGraphicsHost.DrawLine(start, end, color);
        }

        public void DrawCircle(Vector2 position, double radius, SharpDepend.Datatypes.Color color)
        {
            mBuiltInGraphicsHost.DrawCircle(position, radius, color);
        }

        public void EndRender()
        {
            if (mStopwatch.IsRunning)
            {
                mStopwatch.Stop();

                var ticks = mStopwatch.ElapsedTicks;

                mStopwatch.Reset();
            }

            // If the renderer is being rendering before the renderer element has initialized tha size.
            // It will be null. So null check it before.
            var bitmap = wBitmap;
            if (bitmap != null)
            {
                mBuiltInGraphicsHost.EndRender();

                // TODO: Optimize this...
                resultLength = mBuiltInGraphicsHost.BackBuffer.Length * sizeof(int);
                if (resultLength > result.Length)
                {
                    result = new byte[resultLength];
                }
                System.Buffer.BlockCopy(mBuiltInGraphicsHost.BackBuffer, 0, result, 0, resultLength);
            }

            mStopwatch.Start();
        }

        public void DisposeDevice()
        {
            timer.Stop();
            SizeChanged -= SoftwareRenderer_SizeChanged;
            wBitmap = null;
        }

        /// <summary>
        /// Get the graphics display if available.
        /// </summary>
        public IGraphicsDisplay GraphicsDisplay { get; private set; }

        /// <summary>
        /// Calls wqhen this instance has been created.
        /// </summary>
        /// <param name="graphicsDisplay"></param>
        public void Initialize(IGraphicsDisplay graphicsDisplay)
        {
            GraphicsDisplay = graphicsDisplay;
        }

        public void DrawTriangles(FastStructList<Vector2> vertices, FastStructList<ushort> indices, FastStructList<Color> colors)
        {
            throw new NotImplementedException();
        }

        public System.Collections.ObjectModel.ReadOnlyCollection<Texture> Textures
        {
            get { return mBuiltInGraphicsHost.Textures; }
        }

        /// <summary>
        /// Always ready.
        /// </summary>
        public bool Ready
        {
            get { return mBuiltInGraphicsHost.Ready; }
        }

        public bool ManualRendering
        {
            get
            {
                return false;
            }
        }
    }
}
